package com.huiyeji.dynamic.core;

import com.alibaba.fastjson.JSON;
import com.huiyeji.crypt.R2Coder;
import com.huiyeji.crypt.R3Coder;
import com.huiyeji.crypt._R_2_3_Coder;
import com.huiyeji.dynamic.core.annotations.DynamicBean;
import com.huiyeji.dynamic.core.annotations.DynamicMethod;
import com.huiyeji.dynamic.core.annotations.Private;
import com.huiyeji.dynamic.core.exception.DynamicException;
import sun.net.www.protocol.file.FileURLConnection;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;

/**
 * @author Huiyeji
 */
@DynamicBean("plugin")
public class DynamicPluginManager {

    private final DynamicContext rootContext;

    private final Map<String,PluginObject> globalPlugins = new HashMap<>();

    private final Map<String,String> sourceMappings = new HashMap<>();

    private static final String PLUGIN_FILE_SUFFIX = ".hsp.dy.plugin";

    public DynamicPluginManager(DynamicContext context){
        this.rootContext = context;
    }

    @Private
    public Map<String,PluginObject> getGlobalPlugins(){
        return this.globalPlugins;
    }

    @Private
    protected void registerPlugin(PluginObject pluginObject){
        registerPlugin(null,pluginObject);
    }

    @Private
    protected void registerPlugin(String source,PluginObject pluginObject){
        String pluginName = pluginObject.getDynamicObjectName();

        if (!globalPlugins.containsKey(pluginName)){
            globalPlugins.put(pluginName,pluginObject);
        }

        if (source != null && !"".equals(source)){
            sourceMappings.put(pluginName,source);
            if (!globalPlugins.containsKey(source)){
                globalPlugins.put(source,pluginObject);
            }
        }

    }

    @Private
    public PluginObject getPlugin(String pluginName){
        return this.globalPlugins.get(pluginName);
    }

    @DynamicMethod("import")
    public Set<String> _import(String source){
        if (source == null || "".equals(source)){
            throw new DynamicException(3342,"Plugin Source Name Not Found !");
        }

        Set<String> pluginNames = new HashSet<>();

        PluginObject pluginObject = getPlugin(source);
        if (pluginObject == null){
            //导入插件
            try {
                //本地插件
                if (source.startsWith("file:")){
                    if (!source.endsWith(PLUGIN_FILE_SUFFIX)){
                        throw new RuntimeException("Plugin File Suffix Format Error !");
                    }

                    String pluginName = source.substring(source.lastIndexOf("/") + 1)
                            .replace(PLUGIN_FILE_SUFFIX,"");

                    pluginObject = getPlugin(pluginName);
                    if (pluginObject == null){
                        URL url  = new URL(source);
                        pluginObject = parseStream(pluginName, url.openConnection().getInputStream());
                    }

                    registerPlugin(source,pluginObject);
                    pluginNames.add(pluginName);
                } else if (source.startsWith("http")) {
                    //远程插件
                    URL url  = new URL(source);
                    URLConnection conn = url.openConnection();
                    conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
                    conn.connect();
                    HttpURLConnection urlConn = (HttpURLConnection) conn;
                    if(urlConn.getResponseCode() == HttpURLConnection.HTTP_OK) {
                        String headerField = urlConn.getHeaderField("Content-Disposition");

                        if (headerField == null || !headerField.contains("filename=")){
                            throw new RuntimeException("Plugin File Name Not Found !");
                        }

                        if (!headerField.endsWith(PLUGIN_FILE_SUFFIX)){
                            throw new RuntimeException("Plugin File Suffix Format Error !");
                        }

                        String pluginName = headerField.substring(headerField.indexOf("filename=")+9)
                                .replace(PLUGIN_FILE_SUFFIX,"")
                                .replace("\\","");

                        if (!pluginName.matches(DynamicRegex.OBJECT_NAME.complete())){
                            throw new RuntimeException("Plugin Name Format Error !");
                        }

                        pluginObject = getPlugin(pluginName);
                        if (pluginObject == null){
                            pluginObject = parseStream(pluginName, conn.getInputStream());
                        }

                        registerPlugin(source,pluginObject);
                        pluginNames.add(pluginName);
                    }else{
                        throw new RuntimeException("Plugin Source Not Found !");
                    }
                } else {
                    pluginNames.addAll(_loadRelativePlugins(source));
                }

            } catch (Exception exception) {
                throw new DynamicException(8463,exception.getMessage());
            }

        } else {
            registerPlugin(pluginObject);
            pluginNames.add(pluginObject.getDynamicObjectName());
        }

        return pluginNames;
    }

    public Set<String> getPluginNames(){
        Set<String> pluginNames = new HashSet<>();
        for (PluginObject pluginObject : globalPlugins.values()) {
            pluginNames.add(pluginObject.getDynamicObjectName());
        }
        return pluginNames;
    }

    public boolean have(String pluginName){
        return globalPlugins.containsKey(pluginName);
    }

    public boolean haveMethod(String pluginName,String methodName){
        PluginObject pluginObject = getPlugin(pluginName);
        if (pluginObject == null){
            return false;
        }

        return pluginObject.haveMethod(methodName);
    }

    protected Set<String> _loadRelativePlugins(String path) {
        try{
            Set<String> pluginNames = new HashSet<>();
            Enumeration<URL> resources = DynamicPluginManager.class.getClassLoader().getResources(path);
            while (resources.hasMoreElements()){
                URL url = resources.nextElement();
                if ("file".equals(url.getProtocol())){
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    File file = new File(filePath);
                    File[] pluginFiles = file.listFiles();
                    if (pluginFiles != null){
                        for (File pluginFile : pluginFiles) {
                            String fileName = pluginFile.getName();
                            if (fileName.endsWith(PLUGIN_FILE_SUFFIX)){
                                String pluginName = fileName.replace(PLUGIN_FILE_SUFFIX, "");
                                PluginObject pluginObject = getPlugin(pluginName);
                                if (pluginObject == null){
                                    pluginObject = parseStream(pluginName, Files.newInputStream(pluginFile.toPath()));
                                }
                                registerPlugin(pluginName,pluginObject);
                                pluginNames.add(pluginName);
                            }
                        }
                    }
                }
            }
            return pluginNames;
        }catch (Exception exception){
            throw new RuntimeException("Dynamic Plugin Manager Load Relative Error : "+exception.getMessage());
        }
    }

    private PluginObject parseStream(String pluginName,InputStream inputStream){
        BufferedInputStream bio = null;

        try {
            bio = new BufferedInputStream(inputStream);

            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            int nRead;
            byte[] data = new byte[16384];
            while ((nRead = bio.read(data, 0, data.length)) != -1) {
                buffer.write(data, 0, nRead);
            }

            byte[] byteArray = buffer.toByteArray();

            return buildPlugin(pluginName, byteArray, 0);
        } catch (Exception exception){
            throw new DynamicException(8463,exception.getMessage());
        } finally {
            try {
                if(bio!=null){
                    bio.close();
                }
            } catch(IOException e){
                e.printStackTrace();
            }
        }
    }

    /**
     * 安装插件
     * @param source 插件资源地址 (支持本地插件 和 远程插件)
     */
    public Set<String> install(String source){
        return _import(source);
    }

    /**
     * 卸载插件
     */
    public void uninstall(String pluginName){
        PluginObject pluginObject = getPlugin(pluginName);
        if (pluginObject != null){
            pluginObject.interrupt();
        }
        globalPlugins.remove(pluginName);
        String source = sourceMappings.get(pluginName);
        if (source != null && !source.equals("")){
            globalPlugins.remove(source);
        }
        sourceMappings.remove(pluginName);
    }

    /**
     * 全部卸载
     */
    public void uninstall(){
        globalPlugins.clear();
        sourceMappings.clear();
    }

    private PluginObject buildPlugin(String pluginName,byte[] bytes,int type){
        try {
            String content ="";
            if (type == 0){
                content = new String(bytes, StandardCharsets.UTF_8);
            } else if (type == 3){
                content = R3Coder.instance.decode(new String(bytes));
            } else if (type == 2){
                content = new String(R2Coder.instance_1.decode(bytes),StandardCharsets.UTF_8);
            } else if (type == 1){
                content = new String(_R_2_3_Coder.decode(bytes),StandardCharsets.UTF_8);
            }
            Map<String,Object> dataMap = (Map<String,Object>) JSON.parseObject(content, Map.class);
            List<String> initializeStatements = (List<String>) dataMap.get("initialize");
            Map<String,List<String>> methodStatements = (Map<String,List<String>>) dataMap.get("methods");
            if (initializeStatements == null || methodStatements == null){
                throw new RuntimeException();
            }

            try {
                return new PluginObject(pluginName,initializeStatements,methodStatements,rootContext);
            }catch (Exception exception){
                throw new DynamicException(8970, exception.getMessage());
            }
        } catch (DynamicException exception){
            throw exception;
        } catch (Exception exception){
            if (type == 0){
                //R3
                return buildPlugin(pluginName, bytes,3);
            } else if (type == 3){
                //R2.1
                return buildPlugin(pluginName, bytes,2);
            } else if (type == 2) {
                //R23
                return buildPlugin(pluginName, bytes,1);
            } else {
                throw new RuntimeException("Plugin Parse Failed : " + exception.getMessage());
            }
        }
    }

    public static void main(String[] args) throws IOException {
        Enumeration<URL> resources = DynamicPluginManager.class.getClassLoader().getResources("plugins/");
        while (resources.hasMoreElements()){
            URL url = resources.nextElement();
            if ("file".equals(url.getProtocol())){
                String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                File file = new File(filePath);
                File[] pluginFiles = file.listFiles();
                for (File pluginFile : pluginFiles) {
                    new FileInputStream(pluginFile);
                }
            }
        }
        System.out.println(resources);
    }
}
